from math import sin, cos, pi, atan

TWO_PI = 2 * pi

class Object2D(object):
    def __init__(self, pos=(0, 0), heading=0.0, shape=None, master=None):
        self.__x = pos[0]
        self.__y = pos[1]
        self.__heading = heading % TWO_PI
        self.__shape = shape
        self.__master = None
        self.__bindings = {}
        if master is not None:
            master.add(self)
            
    def set_master(self, master):
        self.__master = master
        
    def bind(self, sequence, function):
        self.__bindings[sequence] = function
        if self.__master is not None:
            self.__master.bind(self, sequence, function)
            
    def unbind(self, sequence):
        del self.__bindings[sequence]
        if self.__master is not None:
            self.__master.unbind(self, sequence)
            
    def iter_bindings(self):
        return self.__bindings.iteritems()
        
    def shape_updated(self):
        if self.__master is not None:
            self.__master.update_shape(self)
            
    def move(self, dx=0, dy=0):
        self.__setpos((self.__x + dx, self.__y + dy))
        
    def move_forward(self, n):
        self.__x += cos(self.__heading) * n
        self.__y += sin(self.__heading) * n
        if self.__master is not None:
            self.__master.update_position(self)
            
    def move_backward(self, n):
        self.__x -= cos(self.__heading) * n
        self.__y -= sin(self.__heading) * n
        if self.__master is not None:
            self.__master.update_position(self)
            
    def move_right(self, n):
        self.__x += cos(self.__heading - pi/2) * n
        self.__y += sin(self.__heading - pi/2) * n
        if self.__master is not None:
            self.__master.update_position(self)
            
    def move_left(self, n):
        self.__x += cos(self.__heading + pi/2) * n
        self.__y += sin(self.__heading + pi/2) * n
        if self.__master is not None:
            self.__master.update_position(self)
            
    def rotate(self, angle):
        self.__heading = (self.__heading + angle) % TWO_PI
        if self.__master is not None:
            self.__master.update_heading(self)
            
    def rotate_to(self, object2d):
        self.__heading = atan(float(object2d.__y - self.__y) / float(object2d.__x - self.__x))
        if object2d.__x < self.__x:
            self.__heading += pi
        if self.__master is not None:
            self.__master.update_heading(self)
            
    def __getx(self): return self.__x
    def __setx(self, x):
        self.__x = x
        if self.__master is not None:
            self.__master.update_position(self)
            
    def __gety(self): return self.__y
    def __sety(self, y):
        self.__y = y
        if self.__master is not None:
            self.__master.update_position(self)
            
    def __getpos(self): return self.__x, self.__y
    def __setpos(self, (x, y)):
        self.__x = x
        self.__y = y
        if self.__master is not None:
            self.__master.update_position(self)
            
    def __getheading(self): return self.__heading
    def __setheading(self, heading):
        self.__heading = heading % TWO_PI
        if self.__master is not None:
            self.__master.update_heading(self)
        
    def __getshape(self): return self.__shape
    def __setshape(self, shape):
        self.__shape = shape
        if self.__master is not None:
            self.__master.update_shape(self)
            
    x       = property(__getx, __setx)
    y       = property(__gety, __sety)
    pos     = property(__getpos, __setpos)
    heading = property(__getheading, __setheading)
    shape   = property(__getshape, __setshape)
    
